package tst.xml.parent

/**
 * Works on the assumptions found in the DiscussionSummaries wiki page http://code.google.com/p/scala-xml-extensions/wiki/DiscussionSummaries
 * section "Using the ParentMap? to allow changes to the XML hierarchy"
 * 
 */
object TestModifications {

  import TestParent._
  
      var xml = 
      <root>
        <person>
          <name>fred</name>
          <addr>addr1</addr>
        </person>
        <person>
          <name>james</name>
          <addr>addr2</addr>
        </person>
        <bills>
          <bill id="bill1">
            <addr>addr1</addr>
          </bill>
        </bills>
      </root>

  import scala.io.Source
  import scalaxx.xml.{Document,Node, Elem}
  import scalaxx.xml.parsing.ConstructingParser
    
  def swapChild( oldElem : Elem, newElem: Elem, p : Elem , children : Seq[ Node ] ) = Elem(p.prefix, p.label, p.attributes, p.scope, 
                    ( children.map { x => if (x eq oldElem) {
                                       newElem
                                     } else {
                                       x
                                     }
                      } ):_*)
    
  /**
   * Performs all stages of the algorithm
   * 
   * elem is the new item, parentNode is the old parent node    
   */
  def recreateMap( oldElem : Elem, newElem : Elem, parentMap : ParentMap) : ParentMap = {

    /**
     * creates the new parents, removing the old parent maps for them
     */
    def createNewParents( oldElem : Elem, elem : Elem, parentMap : ParentMap, oldParent : Elem, list : List[Node] ) : ( ParentMap, List[Node] ) = {
  
	    if ((oldParent eq ROOT_NODE) || oldParent == null) {
	      // we are the root, nowhere to go?
	      (parentMap, list)
	    } else {
	    
	      // swap the children
	      val newParent = swapChild( oldElem, elem, oldParent, oldParent.child);
	    
	      // remove the old mapping
	      var pm = ParentMap( parentMap.factory, parentMap.map - (parentMap.factory.createNodePointer(oldParent)) )
       
          if (parentMap.get(oldParent) isEmpty) {
            (pm, newParent :: list )
          } else {
            createNewParents( oldParent, newParent, pm , parentMap( oldParent ), newParent :: list )
          }
	    }
    }
    
    var (pm, list) = createNewParents( oldElem, newElem, parentMap, parentMap(oldElem), Nil)
    
    pm = ParentMap( pm.factory, pm.map - (pm.factory.createNodePointer(oldElem)) )
    
    // reset the immediate elements children
    for{ xml <- oldElem.child } {
      // add new
      pm = ParentMap( pm.factory, pm.map + 
                        (pm.factory.createNodePointer(xml) -> newElem ) )
    }
    
    for{ xml <- list
      child <- xml.child
    } {
      println(child)
    
      // add new
      pm = ParentMap( pm.factory, pm.map + 
                        (pm.factory.createNodePointer(child) -> xml.asInstanceOf[Elem]) )
    
    }
    
    pm
  } 
  
  /**
   * returns the parent node, if any and the map
   */
  def removeElement( elem : Elem, parentMap : ParentMap ) : (Option[Elem], ParentMap) = {
    
    // for each of the parent mappings, remove this node.  We shouldn't remove now empty nodes!! Just remap them
    val p =  parentMap( elem )
    
    def removeChild( elem: Elem, p : Elem , children : Seq[ Node ] ) = Elem(p.prefix, p.label, p.attributes, p.scope, 
                    ( children.filter { x => !( x eq elem ) } ):_*)
    
    val newParent = removeChild(elem, p, p.child )
    
    // TODO clean the map for the old ones ???? 
    
    var pm = parentMap
    
    
    ( Some( newParent ) , recreateMap( p, newParent, pm ) )
  }
  
  def modify( orig : Elem, parentMap : ParentMap, f : (Elem) => Option[Elem]) : (Option[Elem], ParentMap) = {
    
    val res = f(orig)
    if (res.isEmpty) {
      // its a delete
      removeElement( orig, parentMap)
    } else {
      ( res, parentMap )
    }
  }
      
  def main(args : Array[String]) : Unit = {
    
        // primitive conversion to scalaxx.xml
    val xxml = scalaxx.xml.Utility.trim(ConstructingParser.fromSource(Source.fromString(xml.toString), true).document.docElem)
    
    val factory = IdentityNodePointerFactory
    implicit val map = factory.createParentNodes(xxml)
    
    val deleteFromMap = ( ( xxml \\ "person" ) (1) \ "addr" last ) .asInstanceOf[Elem]
    
    val (parent, newMap) = removeElement( deleteFromMap , map)
    
    // new root will now have no 2nd person
    println( parent )
    println( newMap )
    
  }
  
}
